﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Circle.Domain.Interface.Service;
using Circle.Domain.Interface.Repository;
using Circle.Domain.Entity;
using System.Data.Entity;
using Circle.Domain.Application;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using Circle.Utility;
using System.Security.Principal;
using Circle.Domain.Model;

namespace Circle.Service
{
    public class MembershipService :BaseCircleService, IMembershipService
    {
        private static readonly int PASSWORD_SALT_SIZE = 24;
        //支持中文，数字，大小写字母，下划线！不限制哪个开头哪个结束，2到18位！不能有除了这几个的其他任何字符，包括空格！
        private static readonly string REGEX_PATTERN_USERNAME = @"^([\w\d_]|[\u4e00-\u9fa5]){2,18}$";
        private static readonly string REGEX_PATTERN_EMAIL = @"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$";
        private static readonly string REGEX_PATTERN_PASSWORD = @"(.){6,}";
        //letter, number, underscore, and 
       // private static readonly string regex_Pattern_Password = @"^[a-zA-Z0-9#$@\_]{6,18}$";

        private readonly IRepository<MemberUser> _userRepository;
        private readonly IRepository<MemberRole> _roleRepository;
        private readonly ISettingService _settingService;
        private readonly IEmailService _emailService;
        private readonly ICacheService _cacheManagerService;
        private readonly ILoggingService _loggingService;


        public MembershipService(IRepository<MemberUser> repositoryUser,
                                 IRepository<MemberRole> repositoryRole,
                                 ISettingService settingService,
                                 IEmailService emailService,
                                 ICacheService cacheManagerService,
                                 ILoggingService loggingService)
        {
            _roleRepository = repositoryRole;
            _userRepository = repositoryUser;
            _settingService = settingService;
            _emailService = emailService;
            _cacheManagerService = cacheManagerService;
            _loggingService = loggingService;
        }

        #region User Creation & Authentication

        private bool IsValidUserName(string userName)
        {
           if (string.IsNullOrWhiteSpace(userName)) return false;
           return Regex.IsMatch(userName, REGEX_PATTERN_USERNAME);
        }

        private bool IsValidEmail(string email)
        {
            if (string.IsNullOrWhiteSpace(email)) return false;
            return Regex.IsMatch(email, REGEX_PATTERN_EMAIL);
        }

        private bool IsValidPassword(string password)
        {
            if (string.IsNullOrWhiteSpace(password)) return false;
            return Regex.IsMatch(password, REGEX_PATTERN_PASSWORD);
        }

        private OperationStatus IsDuplicated(string userName, string registerEmail)
        {
           var existingUsers = AllUsers.Where(x => x.UserName == userName 
                                                || x.RegisterEmail == registerEmail).ToList();

           if (existingUsers.Count(x => x.UserName.Trim().ToLower() == userName.Trim().ToLower()) != 0)
           {
              return OperationStatus.DuplicatedUserName;
           }

           if (existingUsers.Count(x => x.RegisterEmail.Trim().ToLower() == registerEmail.Trim().ToLower()) != 0)
           {
              return OperationStatus.DuplicatedRegisterEmail;
           }

           return OperationStatus.None;
        }

        private OperationStatus CheckIntegrity(MemberUser user)
        {
            if (!IsValidUserName(user.UserName)) return OperationStatus.InvalidUserName;
            if (!IsValidEmail(user.RegisterEmail)) return OperationStatus.InvalidEmail;
            if (!IsValidPassword(user.Password)) return OperationStatus.InvalidPassword;
            return IsDuplicated(user.UserName, user.RegisterEmail);
        }

        private string CreateSalt(int size)
        {
            //TODO: FIPS Error
            /*
            // Generate a cryptographic random number.
            var rng = new RNGCryptoServiceProvider();
            var buff = new byte[takeAmount];
            rng.GetBytes(buff);

            // Return a Base64 string representation of the random number.
            return Convert.ToBase64String(buff);
             * */

            return string.Empty;
        }

        private string GenerateSaltedHash(string plainText, string salt)
        {
            // http://stackoverflow.com/questions/2138429/hash-and-salt-passwords-in-c-sharp

            //TODO: FIPS Error
            /*
            var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            var saltBytes = Encoding.UTF8.GetBytes(salt);

            // Combine the two lists
            var plainTextWithSaltBytes = new List<byte>(plainTextBytes.Length + saltBytes.Length);
            plainTextWithSaltBytes.AddRange(plainTextBytes);
            plainTextWithSaltBytes.AddRange(saltBytes);

            // Produce 256-bit hashed value i.e. 32 bytes
            HashAlgorithm algorithm = new SHA256Managed();
            var byteHash = algorithm.ComputeHash(plainTextWithSaltBytes.ToArray());
            return Convert.ToBase64String(byteHash);
             * */

            return plainText;
        }

        private string GenerateToken()
        {
            return Guid.NewGuid().ToString();
        }

        private void SanitizeUser(MemberUser circleUser)
        {
            circleUser.UserName = StringUtility.SafePlainText(circleUser.UserName);
            circleUser.RegisterEmail = StringUtility.SafePlainText(circleUser.RegisterEmail);
            circleUser.ContactEmail = StringUtility.SafePlainText(circleUser.ContactEmail);
            circleUser.Password = StringUtility.SafePlainText(circleUser.Password);
            circleUser.PasswordAnswer = StringUtility.SafePlainText(circleUser.PasswordAnswer);
            circleUser.Avatar = StringUtility.SafePlainText(circleUser.Avatar);
            circleUser.Comment = StringUtility.SafePlainText(circleUser.Comment);
            circleUser.PasswordQuestion = StringUtility.SafePlainText(circleUser.PasswordQuestion);
            circleUser.Signature = StringUtility.GetSafeHtml(circleUser.Signature);
        }

        public OperationStatus CreateUser(MemberUser newUser)
        {
            var status = CheckIntegrity(newUser);
            if (status != OperationStatus.None) return status;
            
            SanitizeUser(newUser);

            var defaultSetting = _settingService.GetDefaultSetting();
            newUser.PasswordSalt = CreateSalt(PASSWORD_SALT_SIZE);
            newUser.Password = GenerateSaltedHash(newUser.Password, newUser.PasswordSalt);
            //newUser.Role = defaultSetting.NewMemberStartRole;
            newUser.RoleId = defaultSetting.NewMemberStartRoleId;
            newUser.CreateDate = DateTime.UtcNow;
            newUser.IsLockedOut = false;
            newUser.Slug = newUser.UserName;
            newUser.Token = GenerateToken();
            newUser.TokenGeneratedTime = DateTime.UtcNow;
            if (defaultSetting.EmailVerifyOnNewUser)
            {
                newUser.IsApproved = false;
            }
            else
            {
                newUser.IsApproved = defaultSetting.AutoApproveNewMember;
                newUser.LastLoginDate = DateTime.UtcNow;
            }

            try
            {
                _userRepository.Add(newUser);
                //_userRepository.SaveChanges();
                if (defaultSetting.EmailVerifyOnNewUser)
                {
                    _emailService.SendMail(new Email() { EmailFrom = defaultSetting.AdminEmailAddress, EmailTo = newUser.RegisterEmail, 
                                                         EmailReceiverName = newUser.UserName, NotificationReadon = EmailNotificationReason.UserCreateVerification });
                    return OperationStatus.Verification;
                }
                else if (defaultSetting.EmailSendOnNewUser)
                {
                    _emailService.SendMail(new Email()
                    {
                        EmailFrom = defaultSetting.AdminEmailAddress,
                        EmailTo = newUser.RegisterEmail,
                        EmailReceiverName = newUser.UserName,
                        NotificationReadon = EmailNotificationReason.UserCreateNotification
                    });
                }
                status = OperationStatus.Success;
            }
            catch (Exception ex)
            {
                _loggingService.Error(ex.Message);
                status = OperationStatus.GenericError;
            }

            return status;
        }

        #endregion

        public MemberUser CreateEmptyUser()
        {
            var defaultSetting = _settingService.GetDefaultSetting();
            var user =  new MemberUser() { 
                                                IsApproved = true, 
                                                IsLockedOut = false,
                                                IsExternalAccount = false,
                                                CreateDate = DateTime.UtcNow,
                                                RoleId = defaultSetting.NewMemberStartRoleId
                                             };
          
           // item.Role = _roleRepository.Single(r => r.Id == item.RoleId); //allroleItems

            user.Role = AllRoleItems.Single(r => r.Id == user.RoleId);
            return user;
        }

        public MemberRole CreateEmptyRole()
        {
            return new MemberRole() { IsAdmin = false };
        }

        public MemberUser UpdatePassword(MemberUser user)
        {
            if (string.IsNullOrEmpty(user.PasswordSalt))
            {
                user.PasswordSalt = CreateSalt(PASSWORD_SALT_SIZE);
            }
            user.Password = "M123";

            return user;
        }

        public bool IsAdminUser(MemberUser user)
        {
            return user.Role.IsAdmin;
        }

        public IQueryable<MemberUser> AllUsers
        {
            get { return _userRepository.All.Include(x => x.Role); }
        }

        public IQueryable<MemberRole> AllRoles
        {
            get { return _roleRepository.All; }
        }

        public List<MemberUser> AllUserItems
        {
            get
            {
                return AllUsers.ToList();
            }
        }

        /*** Role need to cache, while item doesn't * **/
        public List<MemberRole> AllRoleItems
        {
            get
            {
                if (!_cacheManagerService.Contains("MembershipService_AllRoleItems"))
                {
                    _cacheManagerService.AddOrUpdate("MembershipService_AllRoleItems", AllRoles.ToList());
                }
                return _cacheManagerService.GetData<List<MemberRole>>("MembershipService_AllRoleItems");
            }
        }

        public MemberUser GetUser(Guid id)
        {
            return _userRepository.Get(id);
        }

        public MemberRole GetRole(Guid id)
        {
            return AllRoleItems.Where(x => x.Id == id).SingleOrDefault();
        }

        public IQueryable<MemberUser> GetUsersByRole(MemberRole role)
        {
            return _userRepository.All.Where(u => u.Role == role);
        }

        public IQueryable<MemberRole> GetRolesByUser(MemberUser user)
        {
            return _roleRepository.All.Where(r => r.Users.Contains(user));
        }

        public OperationStatus SaveUser(MemberUser user)
        {
            //ServiceHelper.Save<MembershipUser>(_userRepository, item);
            OperationStatus status = OperationStatus.None;
            try
            {
                if (user.IsNew())
                {
                    status = CreateUser(user);
                    //item.Role = AllRoleItems.Where(r => r.Id == item.RoleId).SingleOrDefault();
                }
            }
            catch (Exception)
            {
                status = OperationStatus.GenericError;
            }

            return status;
        }
    
        public void DeleteUser(MemberUser user)
        {
            ServiceHelper.Delete<MemberUser>(_userRepository, user);
        }

        public void SaveRole(MemberRole role)
        {
            ServiceHelper.Save<MemberRole>(_roleRepository, role);
            ServiceHelper.ClearCache(_cacheManagerService);
        }

        public void DeleteRole(MemberRole role)
        {
            ServiceHelper.Delete<MemberRole>(_roleRepository, role);
            ServiceHelper.ClearCache(_cacheManagerService);
        }

        public bool ValidateUser(string identifier, string password, out MemberUser user)
        {
            if (string.IsNullOrWhiteSpace(identifier) || string.IsNullOrWhiteSpace(password))
            {
                user = null;
            }
            else
            {
                user = _userRepository.All.Include(x => x.Role).Where(x => (x.UserName == identifier || x.RegisterEmail == identifier)
                                                                && x.Password == password).SingleOrDefault();
            }
            return user != null;
            
        }

        public bool CheckUserIdentity(string userName, string registerEmail) //, string contactEmail
        {
            List<string> identityList = new List<string>();
            identityList.Add(userName.ToLower());
            identityList.Add(registerEmail.ToLower());
            var user = _userRepository.All.Where(x => identityList.Contains(x.UserName.ToLower())
                                         && identityList.Contains(x.RegisterEmail.ToLower())).SingleOrDefault();
            return user == null;
        }


    }
}
